package com.sbs.demo.config;

import com.sbs.demo.common.ResultCode;
import com.sbs.demo.common.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * @author Administrator
 */
@Configuration
@Slf4j
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        SSLContext sslContext = SSLContextBuilder.create().setProtocol(SSLConnectionSocketFactory.SSL).loadTrustMaterial((x, y) -> true).build();
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create()
                .setMaxConnPerRoute(10000)
                .setMaxConnTotal(10000)
                .setConnectionManager(poolingConnectionManager())
                .setKeepAliveStrategy(keepAliveStrategy())
                .setSSLContext(sslContext).setSSLHostnameVerifier((x, y) -> true);
        httpClientBuilder.evictExpiredConnections();
        httpClientBuilder.evictIdleConnections(10, TimeUnit.MINUTES);

        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
        httpRequestFactory.setConnectionRequestTimeout(600000);
        httpRequestFactory.setConnectTimeout(600000);
        httpRequestFactory.setReadTimeout(600000);

        RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(httpRequestFactory));
        restTemplate.setErrorHandler(restTemplateErrorHandler());
        restTemplate.getInterceptors().add(restTemplateLogInterceptor());

        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
                MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM,
                MediaType.TEXT_PLAIN, MediaType.TEXT_HTML
        ));
        restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);

        return restTemplate;
    }

    @Bean
    public HttpClientConnectionManager poolingConnectionManager() {
        PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
        poolingConnectionManager.setMaxTotal(20000);
        poolingConnectionManager.setDefaultMaxPerRoute(20000);
        poolingConnectionManager.setValidateAfterInactivity(30000);
        return poolingConnectionManager;
    }

    private DefaultResponseErrorHandler restTemplateErrorHandler() {
        return new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) {
                log.info(response.toString());
            }
        };
    }

    private ConnectionKeepAliveStrategy keepAliveStrategy() {
        return new DefaultConnectionKeepAliveStrategy() {
            @Override
            public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) {
                long keepAlive = super.getKeepAliveDuration(response, context);
                return keepAlive == -1 ? 1000 : keepAlive;
            }
        };
    }

    private ClientHttpRequestInterceptor restTemplateLogInterceptor() {
        return (httpRequest, bytes, clientHttpRequestExecution) -> {
            ClientHttpResponse response;
            try {
                httpRequest.getHeaders().setAcceptCharset(Collections.singletonList(StandardCharsets.UTF_8));
                long startTime = System.currentTimeMillis();
                log.info("调用外部接口开始 url:{}, method:{}, headers:{}, body:{}",
                        httpRequest.getURI(),
                        httpRequest.getMethodValue(),
                        httpRequest.getHeaders(),
                        new String(bytes, StandardCharsets.UTF_8));
                response = clientHttpRequestExecution.execute(httpRequest, bytes);
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8));
                StringBuilder responseBody = new StringBuilder();
                String line = bufferedReader.readLine();
                while (line != null) {
                    responseBody.append(line);
                    line = bufferedReader.readLine();
                }
                log.info("调用外部接口返回结果 url:{}, method:{}, Authorization:{}, body:{}, httpStatus:{}, time:{}, response:{}",
                        httpRequest.getURI(),
                        httpRequest.getMethodValue(),
                        httpRequest.getHeaders().get("Authorization"),
                        new String(bytes, StandardCharsets.UTF_8),
                        response.getRawStatusCode(),
                        System.currentTimeMillis() - startTime,
                        responseBody);
            } catch (IOException e) {
                log.error("外部调用接口失败 url:{}, method:{}, headers:{}, body:{}",
                        httpRequest.getURI(),
                        httpRequest.getMethodValue(),
                        httpRequest.getHeaders(),
                        new String(bytes, StandardCharsets.UTF_8),
                        e);
                if (e instanceof SocketTimeoutException) {
                    throw new ServiceException(ResultCode.API_TIMEOUT);
                }
                throw new ServiceException(ResultCode.API_NETWORK_ERROR);
            }
            return response;
        };
    }
}
